package dispatcher

import (
	"math/rand"
	"time"

	"gitee.com/sillyman/mixlog"

	"gitee.com/sillyman/WebCrawler/requester"
	"gitee.com/sillyman/WebCrawler/urlqueue"
)

// dispatcher 调度员
type dispatcher struct {
	//  requester HTTP请求者
	requester *requester.Requester

	// urlQueue URL列队
	urlQueue urlqueue.URLQueue

	// maxNumOfConsecutiveFailures 最大允许的连续故障次数，达到这个数程序将退出
	maxNumOfConsecutiveFailures int

	// consecutiveFailureCount 连续故障的计数
	consecutiveFailureCount int

	// visitInterval 最小访问间隔时间
	minVisitInterval time.Duration

	// maxVisitInterval 最大访问间隔时间
	maxVisitInterval time.Duration

	// requestCount 请求计数
	RequestCount int

	// RequestFailureCount 请求故障计数
	RequestFailureCount int

	// quit 程序结束标志
	quitCh chan bool
}

// New 创建调度员实例
func New(r *requester.Requester, q urlqueue.URLQueue, minVisitInterval, maxVisitInterval time.Duration) *dispatcher {
	return &dispatcher{
		requester:                   r,
		urlQueue:                    q,
		maxNumOfConsecutiveFailures: 5,
		minVisitInterval:            minVisitInterval,
		maxVisitInterval:            maxVisitInterval,
		quitCh:                      make(chan bool),
	}
}

// Loop 开始
func (d *dispatcher) Loop(initURLs ...string) {
	// 将初始URL推入列队
	for _, u := range initURLs {
		if err := d.urlQueue.Push(u, ""); err != nil {
			mixlog.Errorf("将初始URL推入列队失败：%s", err.Error())
		}
	}

	mixlog.Info("开始进入循环调度中……")
	for {
		select {
		case <-d.quitCh:
			mixlog.Infof("调度已经退出。共请求了 %d 次，失败了 %d 次。", d.RequestCount, d.RequestFailureCount)
			return
		default:
			d.sleep()
			d.maybeVisiting()
		}
	}
}

// visiting 从 URL 列队中取出一个 URL，然后使用HTTP GET 请求
func (d *dispatcher) maybeVisiting() {
	myURL, err := d.urlQueue.Pop()
	if err != nil {
		mixlog.Errorf("从列队中取出URL失败: %s", err.Error())
		return
	}

	// 列队已空，退出调度
	if myURL == nil {
		mixlog.Info("列队已空，调度将退出……")
		d.Quit()
		return
	}

	// 连续故障次数超过预期
	if d.consecutiveFailureCount >= d.maxNumOfConsecutiveFailures {
		mixlog.Errorf("连续故障次数已达 %d 次，调度将退出……", d.maxNumOfConsecutiveFailures)
		d.Quit()
		return
	}

	mixlog.Debugf("从列队中取出: %s，将访问它……", myURL.String())
	d.RequestCount++
	if err := d.requester.Get(myURL, nil); err != nil {
		d.consecutiveFailureCount++
		d.RequestFailureCount++
		mixlog.Warningf("请求 %q 失败：%s", myURL.String(), err.Error())
		if d.consecutiveFailureCount > 1 {
			mixlog.Warningf("已连续请求失败了 %d 次", d.consecutiveFailureCount)
		}

		mixlog.Infof("因请求失败，将 %q 重新压入列队……", myURL.String())
		if err := d.urlQueue.ForcePush(myURL); err != nil {
			mixlog.Errorf("将 %q 重新压入列队失败：%s", myURL.String(), err.Error())
		}
	} else {
		mixlog.Infof("已成功访问：%s", myURL.String())
		mixlog.Debug("将连续请求失败计数重置为 0")
		d.consecutiveFailureCount = 0
	}
}

// Quit 退出
func (d *dispatcher) Quit() {
	select {
	case <-d.quitCh:
	default:
		close(d.quitCh)
	}
}

// sleep 休眠，根据minVisitIntervalTime和maxVisitIntervalTime，随机休眠时长
func (d *dispatcher) sleep() {
	dur := time.Duration(rand.Int63n(int64(d.maxVisitInterval-d.minVisitInterval))) + d.minVisitInterval
	mixlog.Infof("调度器将休眠：%s", dur.String())
	time.Sleep(dur)
}
